package com.atom.plugin.cleaner

import com.android.build.gradle.AppExtension
import com.atom.plugin.core.AbstractPlugin
import com.atom.plugin.core.Log
import com.atom.plugin.core.concurrent.Schedulers
import com.atom.plugin.core.ext.replaceAll
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.jar.JarEntry


/**
 * All rights Reserved, Designed By www.rongdasoft.com
 * @version V1.0
 * @Title: LogPlugin
 * @Description:
 * @author: wuyongzheng
 * @date: 2022/1/20
 * @Copyright: 2022/1/20 www.rongdasoft.com Inc. All rights reserved.
 */

class CodeCleanPlugin : AbstractPlugin<CodeCleanExtension>() {

    override fun getExtensionName(): String {
        return this.javaClass.simpleName
    }

    override fun getExtensionClass(): Class<CodeCleanExtension> {
        return CodeCleanExtension::class.java
    }

    override fun createExecutor(): ExecutorService {
        return Schedulers.single()
    }
    override fun afterEvaluate(project: Project, app: AppExtension) {
        this.extension.enableUse =
            this.extension.enableUse && !this.extension.cleanPackets.isNullOrEmpty()
        if (this.extension.enableUse) {
            if (this.extension.cleanClasses.isNullOrEmpty()) {
                this.extension.cleanClasses = arrayOf("android/util/Log")
            } else {
                this.extension.cleanClasses = this.extension.cleanClasses?.map {
                    it.replaceAll("\\.", "/")
                }?.toTypedArray()
            }
            this.extension.cleanPackets = this.extension.cleanPackets?.map {
                it.replaceAll("\\.", "/")
            }?.toTypedArray()
        }
        super.afterEvaluate(project, app)
    }

    override fun isTransformEntry(name: String, path: String, isJar: Boolean): Boolean {
        this.extension.cleanPackets?.forEach { packet ->
            if (name.startsWith(packet)) {
                return super.isTransformEntry(name, path, isJar)
            }
        }
        return false
    }

    override fun transformDir(classBytes: ByteArray, inputFile: File, outputFile: File): ByteArray {
        if (this.extension.cleanClasses.isNullOrEmpty()) return classBytes
        val reader = ClassReader(classBytes)
        Log.e("transformDir  #${reader.className}")
        val node = ClassNode()
        reader.accept(node, ClassReader.EXPAND_FRAMES)
        node.methods.forEach { methodNode ->
            val mustBeRemovedLabel: MutableList<AbstractInsnNode> = arrayListOf()
            val instructions = methodNode.instructions
            var next = instructions.first
            while (next != null) {
                if (next is MethodInsnNode) {
                    if (this.extension.cleanClasses!!.contains(next.owner)) {
                        removeLog(next, mustBeRemovedLabel)
                    }
                }
                next = next.next
            }
            if (mustBeRemovedLabel.isNotEmpty()) {
                mustBeRemovedLabel.forEach {
                    methodNode.instructions.remove(it)
                }
                mustBeRemovedLabel.clear()
            }
        }
        val writer = ClassWriter(1)
        node.accept(writer)
        return writer.toByteArray()
    }

    override fun transformJar(
        classBytes: ByteArray,
        entry: JarEntry,
        inputFile: File,
        outputFile: File
    ): ByteArray {
        Log.e("transformJar  ${entry.name} # ${inputFile.absolutePath}")
        return transformDir(classBytes, inputFile, outputFile)
    }

    private fun removeLog(
        node: MethodInsnNode,
        resultDeleteList: MutableList<AbstractInsnNode>
    ) {
        var previous: AbstractInsnNode? = node
        while (previous != null) {
            if (previous is LabelNode) {
                break
            }
            previous = previous.previous
        }

        var next: AbstractInsnNode? = previous?.next
        while (next != null) {
            if (next is LabelNode) {
                break
            }
            next = next.next
            resultDeleteList.add(next.previous)
        }
        next?.also {
            resultDeleteList.add(next)
        }
    }
}